import { isReservedTag } from "../utils/index";

/**
 * 
 * @param {*} vm Vue 实例
 * @param {*} tag 标签
 * @param {*} data 标签对应的属性
 * @param {*} children 该标签下的子元素
 * @param {*} key 该标签的 key
 * @param {*} text 该标签的文本
 * @param {*} componentOptions 组件的配置信息
 * @returns 
 */
function vnode(vm, tag, data, children, key, text, componentOptions) {
  // 这是虚拟节点的信息，可以自定义其他的属性信息，不一定都是语法规定的
  return {
    vm,
    tag,
    data,
    children,
    key,
    text,
    componentOptions
    // ...
  }
}

/**
 * 
 * @param {*} vm Vue 实例
 * @param {*} tag 可以显示到页面的标签
 * @param {*} data 可以显示到页面的标签的属性 props
 * @param  {...any} children 可以显示到页面的该标签的子元素
 * @returns 
 */
export function createElemet(vm, tag, data = {}, ...children) {
  // console.log('createElemet', tag)
  if (isReservedTag(tag)) {
    // 是标签元素
    // console.log('元素')
    return vnode(vm, tag, data, children, data.key, null);
  } else {
    // 是组件,需要创建组件的虚拟节点
    // console.log('组件')
    // 用户的组件配置对象，函数式组件没有类的，因此可能是对象也可能是一个类，
    const Ctor = vm.$options.components[tag];
    // console.log(Ctor)
    return createComponent(vm, tag, data, children, data.key, Ctor);
  }
}
// 将模板变成 AST -> 生成render函数 ->
//  创建虚拟节点（可能是元素，也可能是组件），如果是组件就创建组件的虚拟节点（data.init, 
//    componentOptions存放组件Ctor和子元素）-> 创建真实的节点
// -> 组件会调用 init（创建组件实例并挂载）


// 初始化组件
const init = function (vnode) {
  // console.log('createComponent hook init');
  // new 一个组件，并且 $mount
  const child = vnode.componentInstance = new vnode.componentOptions.Ctor({}); // 放插槽属性
  child.$mount();
}
// 创建一个组件的虚拟节点
function createComponent(vm, tag, data, children, key, Ctor) {
  if (typeof Ctor === "object" && Ctor !== null) {
    const VueCtor = vm.$options._base; // 这个就是 Vue 构造函数
    // console.log(VueCtor)
    Ctor = VueCtor.extend(Ctor); // 也把组件内部声明的 components 包装成一个类
  }

  // 在属性上挂载一些属性，这里通过虚拟节点创造真实节点时，会调用这里的 init 方法
  data.hook = {
    init
  }

  return vnode(vm, tag, data, undefined, key, undefined, { Ctor, children });
}

/**
 * 
 * @param {*} vm Vue 实例
 * @param {*} text 可以显示到页面的字符串文本
 * @returns 
 */
export function createTextVNode(vm, text) {
  // console.log('createTextVNode', text)
  return vnode(vm, undefined, undefined, undefined, undefined, text);
}